// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/constant-pool.h"
#include "src/assembler-inl.h"

namespace v8 {
namespace internal {

#if defined(V8_TARGET_ARCH_PPC)

    ConstantPoolBuilder::ConstantPoolBuilder(int ptr_reach_bits,
        int double_reach_bits)
    {
        info_[ConstantPoolEntry::INTPTR].entries.reserve(64);
        info_[ConstantPoolEntry::INTPTR].regular_reach_bits = ptr_reach_bits;
        info_[ConstantPoolEntry::DOUBLE].regular_reach_bits = double_reach_bits;
    }

    ConstantPoolEntry::Access ConstantPoolBuilder::NextAccess(
        ConstantPoolEntry::Type type) const
    {
        const PerTypeEntryInfo& info = info_[type];

        if (info.overflow())
            return ConstantPoolEntry::OVERFLOWED;

        int dbl_count = info_[ConstantPoolEntry::DOUBLE].regular_count;
        int dbl_offset = dbl_count * kDoubleSize;
        int ptr_count = info_[ConstantPoolEntry::INTPTR].regular_count;
        int ptr_offset = ptr_count * kSystemPointerSize + dbl_offset;

        if (type == ConstantPoolEntry::DOUBLE) {
            // Double overflow detection must take into account the reach for both types
            int ptr_reach_bits = info_[ConstantPoolEntry::INTPTR].regular_reach_bits;
            if (!is_uintn(dbl_offset, info.regular_reach_bits) || (ptr_count > 0 && !is_uintn(ptr_offset + kDoubleSize - kSystemPointerSize, ptr_reach_bits))) {
                return ConstantPoolEntry::OVERFLOWED;
            }
        } else {
            DCHECK(type == ConstantPoolEntry::INTPTR);
            if (!is_uintn(ptr_offset, info.regular_reach_bits)) {
                return ConstantPoolEntry::OVERFLOWED;
            }
        }

        return ConstantPoolEntry::REGULAR;
    }

    ConstantPoolEntry::Access ConstantPoolBuilder::AddEntry(
        ConstantPoolEntry& entry, ConstantPoolEntry::Type type)
    {
        DCHECK(!emitted_label_.is_bound());
        PerTypeEntryInfo& info = info_[type];
        const int entry_size = ConstantPoolEntry::size(type);
        bool merged = false;

        if (entry.sharing_ok()) {
            // Try to merge entries
            std::vector<ConstantPoolEntry>::iterator it = info.shared_entries.begin();
            int end = static_cast<int>(info.shared_entries.size());
            for (int i = 0; i < end; i++, it++) {
                if ((entry_size == kSystemPointerSize)
                        ? entry.value() == it->value()
                        : entry.value64() == it->value64()) {
                    // Merge with found entry.
                    entry.set_merged_index(i);
                    merged = true;
                    break;
                }
            }
        }

        // By definition, merged entries have regular access.
        DCHECK(!merged || entry.merged_index() < info.regular_count);
        ConstantPoolEntry::Access access = (merged ? ConstantPoolEntry::REGULAR : NextAccess(type));

        // Enforce an upper bound on search time by limiting the search to
        // unique sharable entries which fit in the regular section.
        if (entry.sharing_ok() && !merged && access == ConstantPoolEntry::REGULAR) {
            info.shared_entries.push_back(entry);
        } else {
            info.entries.push_back(entry);
        }

        // We're done if we found a match or have already triggered the
        // overflow state.
        if (merged || info.overflow())
            return access;

        if (access == ConstantPoolEntry::REGULAR) {
            info.regular_count++;
        } else {
            info.overflow_start = static_cast<int>(info.entries.size()) - 1;
        }

        return access;
    }

    void ConstantPoolBuilder::EmitSharedEntries(Assembler* assm,
        ConstantPoolEntry::Type type)
    {
        PerTypeEntryInfo& info = info_[type];
        std::vector<ConstantPoolEntry>& shared_entries = info.shared_entries;
        const int entry_size = ConstantPoolEntry::size(type);
        int base = emitted_label_.pos();
        DCHECK_GT(base, 0);
        int shared_end = static_cast<int>(shared_entries.size());
        std::vector<ConstantPoolEntry>::iterator shared_it = shared_entries.begin();
        for (int i = 0; i < shared_end; i++, shared_it++) {
            int offset = assm->pc_offset() - base;
            shared_it->set_offset(offset); // Save offset for merged entries.
            if (entry_size == kSystemPointerSize) {
                assm->dp(shared_it->value());
            } else {
                assm->dq(shared_it->value64());
            }
            DCHECK(is_uintn(offset, info.regular_reach_bits));

            // Patch load sequence with correct offset.
            assm->PatchConstantPoolAccessInstruction(shared_it->position(), offset,
                ConstantPoolEntry::REGULAR, type);
        }
    }

    void ConstantPoolBuilder::EmitGroup(Assembler* assm,
        ConstantPoolEntry::Access access,
        ConstantPoolEntry::Type type)
    {
        PerTypeEntryInfo& info = info_[type];
        const bool overflow = info.overflow();
        std::vector<ConstantPoolEntry>& entries = info.entries;
        std::vector<ConstantPoolEntry>& shared_entries = info.shared_entries;
        const int entry_size = ConstantPoolEntry::size(type);
        int base = emitted_label_.pos();
        DCHECK_GT(base, 0);
        int begin;
        int end;

        if (access == ConstantPoolEntry::REGULAR) {
            // Emit any shared entries first
            EmitSharedEntries(assm, type);
        }

        if (access == ConstantPoolEntry::REGULAR) {
            begin = 0;
            end = overflow ? info.overflow_start : static_cast<int>(entries.size());
        } else {
            DCHECK(access == ConstantPoolEntry::OVERFLOWED);
            if (!overflow)
                return;
            begin = info.overflow_start;
            end = static_cast<int>(entries.size());
        }

        std::vector<ConstantPoolEntry>::iterator it = entries.begin();
        if (begin > 0)
            std::advance(it, begin);
        for (int i = begin; i < end; i++, it++) {
            // Update constant pool if necessary and get the entry's offset.
            int offset;
            ConstantPoolEntry::Access entry_access;
            if (!it->is_merged()) {
                // Emit new entry
                offset = assm->pc_offset() - base;
                entry_access = access;
                if (entry_size == kSystemPointerSize) {
                    assm->dp(it->value());
                } else {
                    assm->dq(it->value64());
                }
            } else {
                // Retrieve offset from shared entry.
                offset = shared_entries[it->merged_index()].offset();
                entry_access = ConstantPoolEntry::REGULAR;
            }

            DCHECK(entry_access == ConstantPoolEntry::OVERFLOWED || is_uintn(offset, info.regular_reach_bits));

            // Patch load sequence with correct offset.
            assm->PatchConstantPoolAccessInstruction(it->position(), offset,
                entry_access, type);
        }
    }

    // Emit and return size of pool.
    int ConstantPoolBuilder::Emit(Assembler* assm)
    {
        bool emitted = emitted_label_.is_bound();
        bool empty = IsEmpty();

        if (!emitted) {
            // Mark start of constant pool.  Align if necessary.
            if (!empty)
                assm->DataAlign(kDoubleSize);
            assm->bind(&emitted_label_);
            if (!empty) {
                // Emit in groups based on access and type.
                // Emit doubles first for alignment purposes.
                EmitGroup(assm, ConstantPoolEntry::REGULAR, ConstantPoolEntry::DOUBLE);
                EmitGroup(assm, ConstantPoolEntry::REGULAR, ConstantPoolEntry::INTPTR);
                if (info_[ConstantPoolEntry::DOUBLE].overflow()) {
                    assm->DataAlign(kDoubleSize);
                    EmitGroup(assm, ConstantPoolEntry::OVERFLOWED,
                        ConstantPoolEntry::DOUBLE);
                }
                if (info_[ConstantPoolEntry::INTPTR].overflow()) {
                    EmitGroup(assm, ConstantPoolEntry::OVERFLOWED,
                        ConstantPoolEntry::INTPTR);
                }
            }
        }

        return !empty ? (assm->pc_offset() - emitted_label_.pos()) : 0;
    }

#endif // defined(V8_TARGET_ARCH_PPC)

} // namespace internal
} // namespace v8
